package com.ecommerce.health;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.cluster.ClusterHealth;
import org.springframework.stereotype.Component;

/**
 * Elasticsearch健康检查组件
 * 负责在应用启动时检查Elasticsearch连接状态，并提供重试机制
 */
// ElasticsearchHealthChecker.java
@Component
@Slf4j
public class ElasticsearchHealthChecker implements ApplicationListener<ApplicationReadyEvent> {

    /**
     * Elasticsearch操作模板
     */
    private final ElasticsearchOperations elasticsearchOperations;

    /**
     * 是否必须依赖Elasticsearch服务
     * true: 连接失败时应用退出
     * false: 连接失败时仅警告
     */
    private final boolean esRequired;

    /**
     * 最大重试次数配置
     */
    @Value("${app.elasticsearch.max-retries}")
    private Integer maxRetries;

    /**
     * 初始重试延迟时间(毫秒)
     */
    @Value("${app.elasticsearch.initial-retry-delay}")
    private Integer initialRetryDelay;

    /**
     * 构造函数
     * @param elasticsearchOperations Elasticsearch操作模板
     * @param esRequired 是否必须依赖ES服务
     */
    public ElasticsearchHealthChecker(ElasticsearchOperations elasticsearchOperations,
                                      @Value("${app.elasticsearch.required:true}") boolean esRequired) {
        this.elasticsearchOperations = elasticsearchOperations;
        this.esRequired = esRequired;
    }

    /**
     * 检查Elasticsearch是否可用
     * @return true表示可用，false表示不可用
     */
    public boolean isElasticsearchAvailable() {
        try {
            return !elasticsearchOperations.cluster().health().isTimedOut();
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 应用启动事件处理
     * 执行Elasticsearch连接检查，根据配置决定失败时的处理方式
     * @param event 应用就绪事件
     */
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        try {
            checkElasticsearchConnectionWithRetry();
        } catch (Exception e) {
            if (esRequired) {
                log.error("========================================================\n" +
                                "Elasticsearch connection check FAILED! Application will exit.\n" +
                                "Error: {}\n" +
                                "========================================================",
                        e.getMessage());
                System.exit(1); // 非正常退出，触发Spring Boot的关闭钩子
            } else {
                log.warn("========================================================\n" +
                                "                    Elasticsearch connection check FAILED! \n" +
                                "                    Application will continue to run but Elasticsearch features will be disabled.\n" +
                                "                    Error: {}\n" +
                                "                    ========================================================",
                        e.getMessage());
            }
        }
    }

    /**
     * 执行Elasticsearch连接检查
     * @throws IllegalStateException 当连接失败或超时时抛出
     */
    private void checkElasticsearchConnection() {
        log.info("Checking Elasticsearch connection...");
        try {
            ClusterHealth health = elasticsearchOperations.cluster().health();
            if (health.isTimedOut()) {
                throw new IllegalStateException("Elasticsearch cluster health check timed out");
            }
            log.info("Elasticsearch connection OK. Cluster: {}, Status: {}",
                    health.getClusterName(), health.getStatus());
        } catch (Exception e) {
            throw new IllegalStateException("Failed to connect to Elasticsearch: " + e.getMessage(), e);
        }
    }

    /**
     * 带重试机制的Elasticsearch连接检查
     * 使用指数退避算法进行重试(initialRetryDelay * 2^(retryCount-1))
     */
    private void checkElasticsearchConnectionWithRetry() {
        int retryCount = 0;
        while (retryCount <= maxRetries) {
            try {
                checkElasticsearchConnection();
                return;
            } catch (Exception e) {
                retryCount++;
                if (retryCount > maxRetries) {
                    throw e;
                }
                log.warn("Elasticsearch connection failed, retrying {}/{}...", retryCount, maxRetries);
                try {
                    Thread.sleep(initialRetryDelay * (long) Math.pow(2, retryCount - 1));
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    throw new IllegalStateException("Interrupted during retry", ie);
                }
            }
        }
    }
}